//
//  SnapKit+DynamicConstraint.swift
//  PMS
//
//  Created by 何海涛 on 16/7/29.
//  Copyright © 2016年 途家. All rights reserved.
//

import Foundation

/**
 *  snap动态约束管理类，用于管理动态约束
 *  这里认为每次调用动态约束创建方法时，创建的一批次的约束集合为一个动态约束管理单元。每个动态约束管理单元里的元素同时添加，同时删除。
 */
public protocol IDynamicConstraintManager {
    /**
     删除视图实例所有动态约束
     
     - parameter forView: 视图实例
     */
    
    func GC()
    
    /**
     删除视图所有的动态约束
     
     - parameter forView: 视图实例
     */
    func tryRemoveDynamicConstraint(_ forView: UIView);
    
    /**
     删除视图某一批次的动态约束
     
     - parameter forView: 视图实例
     - parameter uniqueKey: 唯一的键值
     */
    func tryRemoveDynamicConstraint(_ forView: UIView, uniqueKey: String)
    
    /**
     保存动态约束单元
     
     - parameter forView:     视图实例
     - parameter uniqueKey:   约束单元存储键值
     - parameter constraints: 约束单元（是一组约束实例）
     */
    func trySaveDynamicConstraint(_ forView: UIView, uniqueKey: String, constraints: [Constraint])
}

/// 默认实现一个全局通用的动态约束管理器
/// 应用场景是：平级视图，上下级视图，下上级视图都可能产生动态约束，所以按视图实例来存储是不够的，这里采用代码文件＋行号的方式的进行存储
public class DefaultDynamicConstraintManager : NSObject,  IDynamicConstraintManager {
    public static let Default = DefaultDynamicConstraintManager()
    fileprivate var dynamicConstraintConstainer = Dictionary<String,[Constraint]>()
    fileprivate var uniqueKeyContainer = Dictionary<WeakReferenceWrapper,[String]>()
    
    public func tryRemoveDynamicConstraint(_ forView: UIView) {
        if let refer = uniqueKeyContainer.first({ $0.0.refer() == forView }) {
            for key in refer.1 {
                tryRemoveDynamicConstraint(forView, uniqueKey: key)
            }
        }
    }
    
    public func tryRemoveDynamicConstraint(_ forView: UIView, uniqueKey: String) {
        if let constrints = dynamicConstraintConstainer.removeValue(forKey: uniqueKey) {
           // constrints.forEach({ $0.uninstall() })
            constrints.forEach({$0.deactivate()})
        }
        removeUniqueKey(forView, uniqueKey: uniqueKey)
    }
    
    public func trySaveDynamicConstraint(_ forView: UIView, uniqueKey: String, constraints: [Constraint]) {
        dynamicConstraintConstainer[uniqueKey] = constraints
        saveUniqueKey(forView, uniqueKey: uniqueKey)
    }
    
    fileprivate func saveUniqueKey(_ forView: UIView, uniqueKey: String) {
        if let current = uniqueKeyContainer.first({ $0.0.refer() == forView }) {
            uniqueKeyContainer[current.0] = current.1 + [uniqueKey]
        } else {
            let weakRefer = WeakReferenceWrapper(refer: forView)
            uniqueKeyContainer[weakRefer] = [uniqueKey]
        }
    }
    
    fileprivate func removeUniqueKey(_ forView: UIView, uniqueKey: String) {
        if let current = uniqueKeyContainer.first({ $0.0.refer() == forView }) {
            var modified = current.1
            if let index = modified.index(of: uniqueKey) {
                modified.remove(at: index)
            }
            if modified.count > 0 {
                uniqueKeyContainer[current.0] = modified
            } else {
                uniqueKeyContainer.removeValue(forKey: current.0)
            }
        }
    }
    
    public func GC() {
        for nullRefer in uniqueKeyContainer.filter({ $0.0.isNull }) {
            for key in nullRefer.1 {
                // 因为view引用已经不存在，uninstall逻辑就不需要了
                dynamicConstraintConstainer.removeValue(forKey: key)
            }
            uniqueKeyContainer.removeValue(forKey: nullRefer.0)
        }
    }
}

// extension for dynamic constraints
extension UIView {
    public func snp_updateConstraintsDynamic(_ manager: IDynamicConstraintManager = DefaultDynamicConstraintManager.Default,
                                      file: String = #file, line: UInt = #line, closure: (_ make: ConstraintMaker) -> Void)  {
        let key = DynamicConstraintHelper.createUniqueKey(self, file: file, line: line)
        manager.tryRemoveDynamicConstraint(self, uniqueKey: key)
        self.snp.updateConstraints { (make) in
            closure(make)
            saveDynamicConstraintsAfterInstall(manager, uniqueKey: key, maker: make)
        }
    }
    
    public func snp_makeConstraintsDynamic(_ manager: IDynamicConstraintManager = DefaultDynamicConstraintManager.Default,
                                    file: String = #file, line: UInt = #line, closure: (_ make: ConstraintMaker) -> Void) {
        let key = DynamicConstraintHelper.createUniqueKey(self, file: file, line: line)
        manager.tryRemoveDynamicConstraint(self, uniqueKey: key)
        self.snp.makeConstraints({ make in
            closure(make)
            saveDynamicConstraintsAfterInstall(manager, uniqueKey: key, maker: make)
        })
    }
    
    public func snp_remakeConstraintsDynamic(_ manager: IDynamicConstraintManager = DefaultDynamicConstraintManager.Default,
                                      file: String = #file, line: UInt = #line, closure: (_ make: ConstraintMaker) -> Void) {
        let key = DynamicConstraintHelper.createUniqueKey(self, file: file, line: line)
        manager.tryRemoveDynamicConstraint(self, uniqueKey: key)
        self.snp.remakeConstraints({ make in
            closure(make)
            saveDynamicConstraintsAfterInstall(manager, uniqueKey: key, maker: make)
        })
    }
    
    fileprivate func saveDynamicConstraintsAfterInstall(_ manager: IDynamicConstraintManager, uniqueKey: String, maker: ConstraintMaker) {
        let constraints: [Constraint] = maker.descriptions.map({ $0.constraint }).ofType()
        manager.trySaveDynamicConstraint(self, uniqueKey: uniqueKey, constraints: constraints)
    }
}

public class DynamicConstraintHelper : NSObject {
    /**
     用来创建动态约束单元存储用的唯一键值。
     
     - parameter file: 视图实例
     - parameter file: 调用创建动态约束方法的代码文件
     - parameter line: 调用创建动态约束方法的行号
     
     - returns: file+line
     */
    public static var alert = false
    public static func createUniqueKey(_ view: UIView, file: String, line: UInt) -> String {
        return String(format: "%d:%@:%d", view.hashValue, file, line)
    }
}
